<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  
  <meta name="generator" content="Hugo 0.98.0" />

  
  <meta name="description" content="走在通往幸福的路上">
  

  
  <link rel="apple-touch-icon" sizes="180x180" href="https://blog.v5u.win/apple-touch-icon.png">

  
  <link rel="icon" type="image/png" sizes="32x32" href="https://blog.v5u.win/favicon-32x32.png">

  
  <link rel="icon" type="image/png" sizes="16x16" href="https://blog.v5u.win/favicon-16x16.png">

  
  <link rel="manifest" href="https://blog.v5u.win/site.webmanifest">

  
  <link rel="mask-icon" href="https://blog.v5u.win/safari-pinned-tab.svg" color="">

  <meta name="msapplication-TileColor" content="">

  <meta name="theme-color" content="">

  
  <link rel="stylesheet" href="https://blog.v5u.win/css/bootstrap.min.css" />

  
  <title>正确的使用KVO | 为吾优</title>
  

  <style>
body {
  min-width: 300px;
}

.custom-navbar {
  margin-bottom: 1em;
  height: 60px;
}

.custom-navbar a {
  display: inline-block; 
  padding: 18px 0;
  margin-right: 1em; 
  font-weight: bold; 
}

.custom-navbar a:hover,
.custom-navbar a:focus {
  text-decoration: none; 
}

@media print {
  .custom-navbar {
    display: none;
  }
}

article {
  padding-bottom: 1em;
}

img {
  max-width: 100%;
}


body {
  background-color: #fff;
}



body {
  color: #212529;
}



a {
  color: #007bff;
}



a:hover,
a:focus {
  color: #0056b3;
}



.custom-navbar {
  background-color: #212529;
}



.custom-navbar a {
  color: rgba(255,255,255,.75);
}



.custom-navbar a:hover,
.custom-navbar a:focus {
  color: rgba(255,255,255,1);
}



.container {
  max-width: 800px;
}





</style>
</head>

<body>
  <nav class="custom-navbar">
  <div class="container">
    
    <a href="/">文章</a>
    
    <a href="/tags/">标签</a>
    
    <a href="/about/">关于</a>
    
    <a href="/index.xml">RSS</a>
    
  </div>
</nav>
  
  <div class="container">
    <article>
      <h1>正确的使用KVO</h1>
<p>KVO，全称为Key-Value Observing，是iOS中的一种设计模式，用于检测对象的某些属性的实时变化情况并作出响应。网上广为流传普及的一个例子是利用KVO检测股票价格的变动，例如这里。这个例子作为扫盲入门还是可以的，但是当应用场景比较复杂时，里面的一些细节还是需要改进的，里面有多个地方存在crash的危险。本文旨在逐步递进深入地探讨出一种目前比较健壮稳定的KVO实现方案，弥补网上大部分教程的不足！</p>
<p>首先，假设我们的目标是在一个UITableViewController内对tableview的contentOffset进行实时监测，很容易地使用KVO来实现为。
在初始化方法中加入：</p>
<pre><code>[_tableView addObserver:self forKeyPath:@&quot;contentOffset&quot; options:NSKeyValueObservingOptionNew context:nil];
`&lt;/pre&gt;

在dealloc中移除KVO监听：

&lt;pre&gt;`[_tableView removeObserver:self forKeyPath:@&quot;contentOffset&quot; context:nil];
`&lt;/pre&gt;

添加默认的响应回调方法：

&lt;pre&gt;`- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
       [self doSomethingWhenContentOffsetChanges];
}
`&lt;/pre&gt;

好了，KVO实现就到此完美结束了，拜拜。。。开个玩笑，肯定没这么简单的，这样的代码太粗糙了，当你在controller中添加多个KVO时，所有的回调都是走同上述函数，那就必须对触发回调函数的来源进行判断。判断如下：

&lt;pre&gt;`- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
    if (object == _tableView &amp;amp;&amp;amp; [keyPath isEqualToString:@&quot;contentOffset&quot;]) {

[self doSomethingWhenContentOffsetChanges];

} }
`&lt;/pre&gt;

你以为这样就结束了吗？答案是否定的！我们假设当前类(在例子中为UITableViewController)还有父类，并且父类也有自己绑定了一些其他KVO呢？我们看到，上述回调函数体中只有一个判断，如果这个if不成立，这次KVO事件的触发就会到此中断了。但事实上，若当前类无法捕捉到这个KVO，那很有可能是在他的superClass，或者super-superClass...中，上述处理砍断了这个链。合理的处理方式应该是这样的：

&lt;pre&gt;`- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context
{
    if (object == _tableView &amp;amp;&amp;amp; [keyPath isEqualToString:@&quot;contentOffset&quot;]) {
        [self doSomethingWhenContentOffsetChanges];
} else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
</code></pre>
<p>这样就结束了吗？答案仍旧是否定的。潜在的问题有可能出现在dealloc中对KVO的注销上。KVO的一种缺陷(其实不能称为缺陷，应该称为特性)是，当对同一个keypath进行两次removeObserver时会导致程序crash，这种情况常常出现在父类有一个kvo，父类在dealloc中remove了一次，子类又remove了一次的情况下。不要以为这种情况很少出现！当你封装framework开源给别人用或者多人协作开发时是有可能出现的，而且这种crash很难发现。不知道你发现没，目前的代码中context字段都是nil，那能否利用该字段来标识出到底kvo是superClass注册的，还是self注册的？</p>
<p>回答是可以的。我们可以分别在父类以及本类中定义各自的context字符串，比如在本类中定义context为@&ldquo;ThisIsMyKVOContextNotSuper&rdquo;;然后在dealloc中remove observer时指定移除的自身添加的observer。这样iOS就能知道移除的是自己的kvo，而不是父类中的kvo，避免二次remove造成crash。
文章转自<code>http://www.finalshares.com/read-6914</code> 只为收藏，方便查阅。</p>

    </article>
  </div>

  
  
  

  
</body>

</html>